#include <windows.h>
#include "maplay.h"
#include "mahelper.h"
#include "reader.h"

BOOL PreReadAbort = FALSE;

LONGLONG m_blocksize  = 32768;
int      m_maxBlocks  = 0;
int      m_minBlocks  = 0;

TCHAR dbgMsg[MAX_PATH];
TCHAR dbgMsg2[MAX_PATH];

DWORD WINAPI PreReadThreadFct( LPVOID lpParameter )
{
  try
  {
    CReader *reader = (CReader*)lpParameter;

    while ( PreReadAbort == FALSE )
    {
      {
        CAutoLock lock(&reader->m_csec);

        if (   reader->m_readPos != reader->m_fileSize
          && ( reader->m_preReadActive || reader->m_usedBlocks <= m_minBlocks )
          )
        {
          //if ( !reader->m_preReadActive )
          //    PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Continue reading" );

          reader->m_preReadActive = TRUE;
          CReader::MEMBLOCK *block = reader->m_blocks[reader->m_nextBlock];


          if ( block == NULL )
          {
            block = (CReader::MEMBLOCK*)malloc( sizeof( CReader::MEMBLOCK ) );
            block->data = malloc( m_blocksize );
            reader->m_blocks[reader->m_nextBlock] = block;
            reader->m_usedBlocks++;
            if ( reader->m_nextBlock+1 > reader->m_blockCount )
              reader->m_blockCount = reader->m_nextBlock+1;
          }

          bool success = false;
          if ( block->data != NULL )
          {
            DWORD read;
            if ( ReadFile(reader->m_hFile, block->data, m_blocksize, &read, NULL) )
            {
              block->start = reader->m_readPos;
              block->end   = reader->m_readPos + read - 1;

              reader->m_readPos += read;
              if ( read < m_blocksize )
              {
                // EOF
                reader->m_preReadActive = FALSE;
                //PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"EOF" );
              }
              reader->m_nextBlock++;
              if ( reader->m_nextBlock >= m_maxBlocks )
              {
                reader->m_nextBlock = 0;
                //PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Max blocks full, back to first" );
              }
              success = true;
            }
            else
            {
              int e = GetLastError();

              if ( reader->m_hFile == INVALID_HANDLE_VALUE )
                PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Invalid file handle" );
            }
          }
          else
          {
            PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Malloc error" );
          }

          if ( !success )
          {
            PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Read error, remove block" );
            free( block->data );
            free( block );
            reader->m_blocks[reader->m_nextBlock] = NULL;
            reader->m_usedBlocks--;
          }

          if ( reader->m_usedBlocks == m_maxBlocks /*|| reader->m_nextBlock == reader->m_firstBlock*/ )
          {
            // Buffer full, stop pre-reading
            reader->m_preReadActive = FALSE;
            PostMessage( reader->m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Buffer filled" );
          }
        }
      }

      Sleep( 20 );
    }
  }
  catch(...)
  {
    exit(1);
  }

  return TRUE;
}

CReader::CReader()
{
  m_hFile = INVALID_HANDLE_VALUE;
  m_blockCount = 0;
  m_nextBlock  = 0;
  m_firstBlock = 0;
  m_usedBlocks = 0;
  m_preReadActive = FALSE;
  m_hPreReadThread = NULL;

  if ( m_maxBlocks > 0 )
  {
    m_blocks = new MEMBLOCK*[m_maxBlocks];
    for ( int i=0; i<m_maxBlocks; i++ )
      m_blocks[i] = NULL;
  }
}

CReader::~CReader()
{
  if ( m_maxBlocks > 0 )
  {
    FreeBuffer();
    free( m_blocks );
  }

  if (m_hFile != INVALID_HANDLE_VALUE)
    Close();
}

BOOL CReader::Open(LPCTSTR pszFileName)
{
  PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Open file" );
  CAutoLock lock(&m_csec);
  m_hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, 
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, NULL);
  if (m_hFile == INVALID_HANDLE_VALUE)
  {
    PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Error opening file" );
    return FALSE;
  }

  ULARGE_INTEGER nSize;
  nSize.LowPart = GetFileSize(m_hFile, &nSize.HighPart);
  m_fileSize = nSize.QuadPart;

  m_readPos = 0; m_seekPos = 0;
  FreeBuffer();
  m_preReadActive = FALSE;
  m_hPreReadThread = NULL;

  return TRUE;
}



void CReader::StartPreRead()
{
  //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Start prereading" );
  if ( m_maxBlocks > 0 && m_hFile != INVALID_HANDLE_VALUE )
  {
    PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Start preread thread" );
    m_preReadActive = TRUE;
    PreReadAbort = FALSE;
    m_hPreReadThread = CreateThread( NULL, NULL, PreReadThreadFct, this, CREATE_SUSPENDED, 0 );
    if ( m_hPreReadThread != NULL )
    {
      SetThreadPriority( m_hPreReadThread, THREAD_PRIORITY_NORMAL );
      ResumeThread( m_hPreReadThread );
      Sleep(1000);
      //SetThreadPriority( m_hPreReadThread, THREAD_PRIORITY_BELOW_NORMAL );
      PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Start successful" );
    }
  }
}

void CReader::Close()
{
  CAutoLock lock(&m_csec);

  //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Close file" );

  if ( m_maxBlocks > 0 && m_hPreReadThread != NULL )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Stop preread thread" );
    PreReadAbort = TRUE;
    SetThreadPriority( m_hPreReadThread, THREAD_PRIORITY_ABOVE_NORMAL );
    DWORD exitCode;
    while ( GetExitCodeThread( m_hPreReadThread, &exitCode ) && exitCode == STILL_ACTIVE )
      Sleep( 10 );
    CloseHandle( m_hPreReadThread );
    m_hPreReadThread = NULL;
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Stopped preread thread" );
  }

  FreeBuffer();

  if (m_hFile != INVALID_HANDLE_VALUE)
    CloseHandle(m_hFile);

  m_hFile = INVALID_HANDLE_VALUE;
}

LONGLONG CReader::SetPointer(LONGLONG llMove, DWORD dwMoveMethod)
{
  if (m_hFile == INVALID_HANDLE_VALUE)
    return MAXLONGLONG;

  CAutoLock lock(&m_csec);

  // Calculate new position
  switch( dwMoveMethod )
  {
  case FILE_BEGIN:
    m_seekPos = llMove;
    break;
  case FILE_CURRENT:
    m_seekPos += llMove;
    break;
  case FILE_END:
    m_seekPos = m_fileSize - llMove;
    break;
  }
  // Free no longer required buffer
  //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"SetPointer" );
  FreeBuffer(m_seekPos);

  // Set new file position if read is necessary
  if ( m_maxBlocks == 0 || m_usedBlocks == 0 )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"New file position" );
    LARGE_INTEGER nMove;
    nMove.QuadPart = llMove;
    nMove.LowPart = SetFilePointer(m_hFile, nMove.LowPart, &nMove.HighPart, dwMoveMethod);
    m_readPos = m_seekPos;
  }

  return m_seekPos;
}

BOOL CReader::Read(LPBYTE pbBuff, DWORD cbBuff, LPDWORD pdwRead, BOOL freeBlocks)
{
  if (m_hFile == INVALID_HANDLE_VALUE)
    return FALSE;

  CAutoLock lock(&m_csec);
  BOOL fRet;
  if ( m_maxBlocks == 0 || m_usedBlocks == 0 )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Read from file (nothing in buffer)" );
    if ( m_readPos != m_seekPos )
    {
      LARGE_INTEGER nMove;
      nMove.QuadPart = m_seekPos;
      nMove.LowPart = SetFilePointer(m_hFile, nMove.LowPart, &nMove.HighPart, FILE_BEGIN);
    }
    fRet = ReadFile(m_hFile, pbBuff, cbBuff, pdwRead, NULL);
    if ( !fRet )
      *pdwRead = 0;
    m_seekPos += *pdwRead;
    m_readPos = m_seekPos;
  }
  else
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Read from buffer" );
    fRet = GetFromBuffer(pbBuff, cbBuff, pdwRead, freeBlocks);
    if ( !fRet )
      *pdwRead = 0;
  }

  return fRet;
}

BOOL CReader::GetFromBuffer(LPBYTE pbBuff, DWORD cbBuff, LPDWORD pdwRead, BOOL freeBlocks)
{
  if (m_hFile == INVALID_HANDLE_VALUE)
    return FALSE;

  BOOL fRet = TRUE;

  // Cyclic buffer - i.e. the upcoming block is also the first, unless it's after the current count...
  LONGLONG start=MAXLONGLONG, end=MAXLONGLONG;
  bool finished = false;
  for ( int i=0; i<m_blockCount && !finished; i++ )
  {
    int blockNo = m_firstBlock+i;
    if ( blockNo >= m_blockCount ) blockNo = 0;
    MEMBLOCK *block = m_blocks[blockNo];

    // No more useable blocks
    if ( block == NULL || block->start > m_seekPos+cbBuff )
      break;

    if ( block->start < m_seekPos+cbBuff )
    {
      ULONG sourceOffset = 0;
      ULONG targetOffset = 0;

      if ( block->start > m_seekPos )
      {
        if ( start == MAXLONGLONG )
          start = block->start;
        sourceOffset = 0; //m_seekPos - block->start;
        targetOffset = block->start - m_seekPos;
      }
      else
      {
        if ( start == MAXLONGLONG )
          start  = m_seekPos;
        sourceOffset = m_seekPos - block->start;
        targetOffset = 0;
      }

      if ( m_seekPos + cbBuff-1 <= block->end )
      {
        end = m_seekPos + cbBuff-1;
        finished = true;
      }
      else
      {
        end = block->end;
      }

      UINT size = end - block->start - sourceOffset + 1;

      memcpy( pbBuff + targetOffset, (BYTE*)block->data + sourceOffset, size );
    }
  }

  //swprintf( dbgMsg, L"%d-%d / %d-%d", (int)m_seekPos, (int)cbBuff, (int)start, (int)(end) );
  //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)dbgMsg );

  if ( start > m_seekPos && start != MAXLONGLONG )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Missing beginning" );
    // Read missing beginning
    SetPointer(m_seekPos, FILE_BEGIN);
    ULONG dummy;
    fRet = ReadFile(m_hFile, pbBuff, start-m_seekPos, &dummy, NULL);
    start = m_seekPos;
    m_readPos = start+dummy;
  }

  if ( end < m_seekPos+cbBuff-1 && end != MAXLONGLONG && end+1 != m_fileSize )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Missing end (not enough buffered)" );
    // Read missing end
    LARGE_INTEGER sPos;
    sPos.QuadPart = end+1;
    SetFilePointer( m_hFile, sPos.LowPart, &sPos.HighPart, FILE_BEGIN );
    //SetPointer(end+1, FILE_BEGIN);
    ULONG dummy;
    fRet = ReadFile(m_hFile, pbBuff+(end-m_seekPos+1), m_seekPos+cbBuff-end-1, &dummy, NULL);
    end = end+dummy;
    m_readPos = end+1;
  }

  if ( start == MAXLONGLONG && end == MAXLONGLONG )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Missing completely (not enough buffered)" );
    LARGE_INTEGER sPos;
    sPos.QuadPart = m_seekPos;
    SetFilePointer( m_hFile, sPos.LowPart, &sPos.HighPart, FILE_BEGIN );
    fRet = ReadFile(m_hFile, pbBuff, cbBuff, pdwRead, NULL);
    m_readPos = m_seekPos+*pdwRead;
  }
  else
  {
    *pdwRead = end-start+1;
  }
  m_seekPos += *pdwRead;

  if ( freeBlocks )
  {
    FreeBuffer( m_seekPos );
  }

  return fRet;
}


// WARNING: Lock must be set!
void CReader::FreeBuffer(LONGLONG start)
{
  /*
  if ( start == MAXLONGLONG )
  {
  PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Free all buffers" );
  }
  else
  {
  PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Free beginning" );
  }
  */

  int startBlock = m_firstBlock;
  for ( int i=0; i<m_blockCount; i++ )
  {
    int block = (startBlock + i) % m_blockCount;
    if ( m_blocks[block] != NULL )
    {
      if ( m_blocks[block]->end < start )
      {
        if ( m_blocks[block]->data != NULL )
        {
          free( m_blocks[block]->data );
        }
        free( m_blocks[block] );
        m_blocks[block] = NULL;
        m_usedBlocks--;
        if ( block == m_firstBlock )
          m_firstBlock++;
        if ( m_firstBlock >= m_blockCount ) m_firstBlock = 0;
      }
      else
      {
        // Nothing more to free...
        break;
      }
    }
    else
      break;
  }

  // Free all if new position is not in first block
  if (   start != MAXLONGLONG
    && m_usedBlocks > 0
    && m_blocks[m_firstBlock] != NULL
    && (   m_blocks[m_firstBlock]->start > start
    || m_blocks[m_firstBlock]->end < start
    )
    )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Start not in first block" );
    FreeBuffer();
  }

  if ( m_usedBlocks == 0 )
  {
    //PostMessage( m_hwndMessage, MAP_MSG_STATUS, 42, (DWORD)L"Freed all blocks" );
    m_nextBlock = 0; m_firstBlock = 0;
  }
}


LONGLONG CReader::ScanAudioHeader(int nVersion, int nLayer)
{
  CAutoLock lock(&m_csec);

  DWORD cbLeft, cbRead;
  BYTE bInput[SCAN_BUFFER_SIZE];

  LPBYTE pbCurrent = bInput;
  LONGLONG llRet = SetPointer(0, FILE_CURRENT);

  if ( ! Read(pbCurrent, SCAN_BUFFER_SIZE, &cbRead, FALSE) )
    cbRead = 0;

  cbLeft = cbRead; 
  while (cbLeft > 3)
  {
    while (cbLeft > 3)
    {
      if (CheckAudioHeader(pbCurrent))
      {
        int v = (*(pbCurrent + 1) & 0x10) ?
          2 - ((*(pbCurrent + 1) & 0x08) >> 3): 3;
        int l = 4 - (((*(pbCurrent + 1)) & 0x06) >> 1);
        if (v == nVersion && l == nLayer)
        {
          llRet = SetPointer(0, FILE_CURRENT) - cbLeft;
          SetPointer(llRet, FILE_BEGIN);
          return llRet;
        }
      }
      pbCurrent++;
      cbLeft--;
    }
    memmove(bInput, pbCurrent, cbLeft);
    pbCurrent = bInput + cbLeft;
    cbRead = SCAN_BUFFER_SIZE - cbLeft;
    if ( !Read(pbCurrent, cbRead, &cbRead, FALSE) )
    {
      cbRead=0;
      break;
    }
    cbLeft += cbRead;
    pbCurrent = bInput;
  }
  return MAXLONGLONG;
}

LONGLONG CReader::ScanAudioHeader()
{
  CAutoLock lock(&m_csec);

  DWORD cbLeft, cbRead;
  BYTE bInput[SCAN_BUFFER_SIZE];

  LPBYTE pbCurrent = bInput;
  LONGLONG llRet = SetPointer(0, FILE_CURRENT);

  if ( !Read(pbCurrent, SCAN_BUFFER_SIZE, &cbRead, FALSE) )
    cbRead = 0;

  cbLeft = cbRead; 
  while (cbLeft > 3)
  {
    while (cbLeft > 3)
    {
      if (CheckAudioHeader(pbCurrent))
      {
        llRet = SetPointer(0, FILE_CURRENT) - cbLeft;
        SetPointer(llRet, FILE_BEGIN);
        return llRet;
      }
      pbCurrent++;
      cbLeft--;
    }
    memmove(bInput, pbCurrent, cbLeft);
    pbCurrent = bInput + cbLeft;
    cbRead = SCAN_BUFFER_SIZE - cbLeft;
    Read(pbCurrent, cbRead, &cbRead, FALSE);
    cbLeft += cbRead;
    pbCurrent = bInput;
  }
  return MAXLONGLONG;
}

LONGLONG CReader::GetSize()
{
  //ULARGE_INTEGER nSize;
  //nSize.LowPart = GetFileSize(m_hFile, &nSize.HighPart);
  return m_fileSize;
}
